﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SCALP.BuildDebug;

namespace SCALP
{
    /// <summary>
    /// The main class for compiling CASM code
    /// </summary>
    public static class Compiler
    {
        // string: The original input code
        public static string rawCode;
        // string array: Seperate instructions after the raw code has been split
        public static string[] linedCode;
        // string array: Seperate parts of the instruction currently being worked on
        public static string[] splitCode;
        // byte array: Raw COSIL executable code
        public static byte[] rawExe;

        public static byte[] Compile(string code)
        {
            // set the rawCode to be the code that we need to compile
            rawCode = code;
            // check to make sure the code isn't blank
            if (rawCode == "")
                throw new BuildErrorException("Code Blank", 0);
            // remove all '\r' characters
            rawCode = rawCode.Replace("\r","");
            // swap all space characters coded in the raw code with the numerical representation
            rawCode = rawCode.Replace("\' \'", "32");
            // split the rawCode down into seperate instructions
            linedCode = rawCode.Split('\n');
            // get the raw exe to be the length of the number of instructions needed, with each instruction being 48 bits wide
            //  or 6 8-bit bytes.
            rawExe = new byte[(linedCode.Length * 6)];
            // we need to keep track of where in the exe we are putting the new instruction
            int curLoc = 0;
            // now we must run throught the code and compile each instruction into raw bytecode
            //  and add it to the raw COSIL.
            for (int i = 0; i < linedCode.Length; i++)
            {
                // split the code into individual parts
                splitCode = linedCode[i].Split(' ');
                // check to make sure the instruction isn't too long.
                if (splitCode.Length > 3)
                    throw new BuildErrorException("Instruction too long", (i + 1));
                // get the instruction code
                byte instruction;
                instruction = Instruction.InstructionCreator.Get(splitCode[0]);
                if (instruction == 0)
                    throw new BuildErrorException("Invalid Operation, check spelling", (i + 1));
                // see what addressing mode is being used and parse the instruction
                bool reg1 = false;
                bool reg2 = false;
                Instruction.AddressingMode admode;
                // in here we also need to store the values for creating the instruction so we need the two parameters if they are given
                byte para1 = 0;
                int  para2 = 0;

                if (splitCode.Length >= 2)
                {
                    if (DataType.Char.isChar(splitCode[1]))
                    {
                        reg1 = false;
                        para1 = DataType.Char.parseChar(splitCode[1]);
                    }
                    else if (Instruction.Memory.isRegister(splitCode[1]))
                    {
                        splitCode[1] = splitCode[1].ToUpper();
                        reg1 = true;
                        para1 = Instruction.Memory.getRegister(splitCode[1]);
                    }
                    else
                    {
                        splitCode[1] = splitCode[1].ToUpper();
                        reg1 = false;
                        try
                        {
                            para1 = Byte.Parse(splitCode[1], System.Globalization.NumberStyles.Integer);
                        }
                        catch (FormatException)
                        {
                            throw new BuildErrorException("Invalid value for parameter 1", (i + 1));
                        }
                        catch (OverflowException)
                        {
                            throw new BuildErrorException("Inevitable overflow for parameter 1", (i + 1));
                        }
                    }
                }
                if (splitCode.Length == 3)
                {
                    if (DataType.Char.isChar(splitCode[2]))
                    {
                        reg2 = false;
                        para2 = DataType.Char.parseChar(splitCode[2]);
                    }
                    else if (Instruction.Memory.isRegister(splitCode[2]))
                    {
                        splitCode[2] = splitCode[2].ToUpper();
                        reg2 = true;
                        para2 = Instruction.Memory.getRegister(splitCode[2]);
                    }
                    else
                    {
                        splitCode[2] = splitCode[2].ToUpper();
                        reg2 = false;
                        try
                        {
                            para2 = Int32.Parse(splitCode[2], System.Globalization.NumberStyles.Integer);
                        }
                        catch (FormatException)
                        {
                            throw new BuildErrorException("Invalid value for parameter 2", (i + 1));
                        }
                        catch (OverflowException)
                        {
                            throw new BuildErrorException("Inevitable overflow for parameter 2", (i + 1));
                        }
                    }
                }
                // now to get the addressing mode into the format we need it in
                if (reg1 == true && reg2 == true)
                    admode = Instruction.AddressingMode.RegReg;
                else if (reg1 == true && reg2 == false)
                    admode = Instruction.AddressingMode.RegVal;
                else if (reg1 == false && reg2 == true)
                    admode = Instruction.AddressingMode.ValReg;
                else
                    admode = Instruction.AddressingMode.ValVal;
                // we need a byte instruction
                byte[] operation = Instruction.InstructionCreator.Create(instruction, admode, para1, para2);
                // finally create an instruction and append it to the array
                for (int j = 0; j < operation.Length; j++)
                {
                    rawExe[(j + curLoc)] = operation[j];
                }
                // add six to the current location in the exe as we have just appended a new instruction
                curLoc += 6;
                // let the whole thing go around again
            }
            return rawExe;
        }
    }
}
